package com.itdachang.design.creatation.singleton;

/**
 * 单例模式
 * 1、什么场景用得到？
 *      多线程中的线程池
 *          例子: 不能因为养一条鱼，而创建一个池塘，相关的池，都可以用单例模式
 *      数据库中的线程池
 *      系统环境信息
 *      上下文(ServletContext)
 *      ...
 * 2、面试相关问题
 *      系统中环境信息？【System.getProperties()】
 *      Spring中怎么保证组件单例？
 *      ServletContext是什么？【封装Servlet的信息】
 *      ApplicationContext是什么？ 是单例的吗？怎么保证的？
 *          ApplicationContext: tomcat 一个应用【部署的一个war包】会有一个应用上下文
 *          ApplicationContext: Spring 表示整个IOC容器（怎么保证单例？）ioc容器中很多组件（怎么保证单例）
 *      数据库连接池一般是怎么创建出来的，怎么保证单例？
 *
 * 总结:
 * 单例模式是一种思想，有无数种实现
 * @author gcq
 * @Create 2022-02-06
 */
public class Person {
    /**
     * 单例模式 分为 懒汉 饿汉
     */

    /**
     * person 对象
     * 1、懒汉:没有的时候，那么才去获取
     * 2、饿汉:一上来就直接获取对象
     *      private final static Person instance = new Person()
     */
    private volatile static Person instance;

    /**
     * 构造方法私有化，外部不能实例化
     */
    private Person() {
        System.out.println("实例化了Person");
    }

    /**
     * 考虑到多个线程调用该方法
     * 1、public static synchronized Person getInstance()
     *      直接用 synchronized 锁住类方法，A调用完后，B才能调用
     *          缺点：
     *              麻烦、效率低
     * 2、双重检查锁+内存可见性
     *      效率高、安全
     * @return
     */
    public static Person getInstance() {
        if(instance == null) {
            // 锁住当前类对象
            synchronized (Person.class) {
                // 进来之后，再次判断，没有的话快速释放锁，同时考虑到指令重排，可以给对象加上 volatile
                if(instance == null) {
                    Person person = new Person();
                    instance = person;
                }
            }
        }
        return instance;
    }
}